/*******************************************************************
    Author: Minh B. Do (Arizona State Univ. - binhminh@asu.edu)
 ********************************************************************/
package edu.asu.sapa.ground;

import edu.asu.sapa.Planner;
import edu.asu.sapa.ground.update.Context;

public class MathForm {
	public int type; 
	public static final int Fluent = 0;
	public static final int Constant = 1;
	public static final int DT = 2;
	public static final int Operator = 3;
	public static final int Start = 4;
	public static final int Duration = 5;
	public static final int End = 6;
	
	public static final MathForm Zero = new MathForm(0f);
	public static final MathForm One = new MathForm(1f);
	public static final MathForm NaN = new MathForm(Float.NaN);
	public static final MathForm PInf = new MathForm(Float.POSITIVE_INFINITY);
	public static final MathForm NInf = new MathForm(Float.NEGATIVE_INFINITY);
	public static final MathForm Epsilon = new MathForm(Planner.EPSILON);
	public static final MathForm TwoEpsilon = new MathForm(2*Planner.EPSILON);
	public static final MathForm StartRef = new MathForm(Start);
	public static final MathForm EndRef = new MathForm(End);
	public static MathForm newDurRef() { return new MathForm(Duration); }
	// start and end are only static for the global action
	// and so memory can be shared w.r.t. them
	
	// if its static, it has type 1 and a correct value setting.
	// otherwise the planning graph needs a value, and 1 is okay-ish for both
	// durations and costs.
	public float value=1.0f;

	public int fluent;

	public char op; // +, -, *, /

	public MathForm left;
	public MathForm right;

	public MathForm(int type) {
		this.type = type;
	}

	public MathForm(float f) {
		type = 1;
		value = f;
	}

	public MathForm(Fluent f) {
		type = 0;
		fluent = f.id;
	}

	public MathForm(char o, MathForm l, MathForm r) {
		type = 3;
		op = o;
		left = l;
		right = r;
	}

	public MathForm(MathForm g) {
		type = g.getType();
		value = g.getValue();
		fluent = g.getElement();
		op = g.getOperator();

		if (type == 3) {
			// left = new GMathForm(g.getLeft());
			// right = new GMathForm(g.getRight());
			left = g.getLeft();
			right = g.getRight();
		}
	}

	/**
	 * Set the "Type" of this math formula. 0: function, 1: float value, 2: #t
	 * or intermediate time point (not used for level 3, PDDL21), 3: Non
	 * primitive or complex math formula
	 */
	public MathForm setType(int b) {
		type = b;
		return this;
	}

	public int getType() {
		return type;
	}

	public void setValue(float f) {
		type = 1;
		value = f;
	}

	public float getValue() {
		return value;
	}

	/**
	 * Set the "fluent" of this GMathForm to the Function's ID if the type of
	 * this GMathForm is 0
	 */
	public void setElement(int e) {
		type = 0;
		fluent = e;
	}

	public int getElement() {
		return fluent;
	}

	public void setOperator(char c, MathForm l, MathForm r) {
		type = 3;
		op = c;
		left = l;
		right = r;
	}

	public char getOperator() {
		return op;
	}

	public void setLeft(MathForm m) {
		// left = new GMathForm(m);
		left = m;
	}

	public MathForm getLeft() {
		return left;
	}

	public void setRight(MathForm m) {
		right = m;
	}

	public MathForm getRight() {
		return right;
	}
	
	public float value(Context c) {
		switch (type) {
		case MathForm.Constant:
			return value;
		case MathForm.Operator:
			switch (op) {
			case '+':
				return left.value(c) + right.value(c);
			case '-':
				return left.value(c) - right.value(c);
			case '*':
				return left.value(c) * right.value(c);
			case '/':
				return left.value(c) / right.value(c);
			default:
				return Float.NaN;
			}
		case MathForm.Start:
			return c.start;
		case MathForm.Duration:
			return c.duration;
		case MathForm.End:
			return c.finish;
		case MathForm.Fluent:
		default:
			return Float.NaN;
		}
	}
	
	public float value(State s, Context c) {
		switch (type) {
		case MathForm.Constant:
			return value;
		case MathForm.Operator:
			switch (op) {
			case '+':
				return left.value(s, c) + right.value(s, c);
			case '-':
				return left.value(s, c) - right.value(s, c);
			case '*':
				return left.value(s, c) * right.value(s, c);
			case '/':
				return left.value(s, c) / right.value(s, c);
			default:
				return Float.NaN;
			}
		case MathForm.Start:
			return c.start;
		case MathForm.Duration:
			return c.duration;
		case MathForm.End:
			return c.finish;
		case MathForm.Fluent:
			return s.fluentDB.get(fluent);
		default:
			return Float.NaN;
		}
	}
	
	public float value(State s) {
		switch (type) {
		case MathForm.Constant:
			return value;
		case MathForm.Operator:
			switch (op) {
			case '+':
				return left.value(s) + right.value(s);
			case '-':
				return left.value(s) - right.value(s);
			case '*':
				return left.value(s) * right.value(s);
			case '/':
				return left.value(s) / right.value(s);
			default:
				return Float.NaN;
			}
		case MathForm.Fluent:
			return s.fluentDB.get(fluent);
		case MathForm.Start:
		case MathForm.Duration:
		case MathForm.End:
		default:
			return Float.NaN;
		}
	}
	
	public float value() {
		switch (type) {
		case 1:
			return value;
		case 3:
			float l = left.value();
			if (l != l)
				return Float.NaN;
			float r = right.value();
			if (r != r)
				return Float.NaN;
			type = 1;
			switch (op) {
			case '+': 
				return value = l + r;
			case '-':
				return value = l - r;
			case '*':
				return value = l * r;
			case '/':
				return value = l / r;
			default:
				return value = Float.NaN;
			}
		default:
			return Float.NaN;
		}
	}

	public MathForm bind(float start, float duration, float end) {
		switch (type) {
		case 3:
			MathForm l = left.bind(start, duration, end);
			MathForm r = right.bind(start, duration, end);
			if (l.type == 1 && r.type == 1) {
				float v = 0;
				switch (op) {
				case '+':
					v = l.value + r.value;
					break;
				case '-':
					v = l.value - r.value;
					break;
				case '*':
					v = l.value * r.value;
					break;
				case '/':
					v = l.value / r.value;
					break;
				default:
					return NaN;
				}
				return new MathForm(v);
			}
			if (l != left || r != right)
				return new MathForm(op, l, r);
			return this;
		case 4:
			return new MathForm(start);
		case 5:
			return new MathForm(duration);
		case 6:
			return new MathForm(end);
		case 0:
		case 1:
		case 2:
		default:
			return this;
		}
	}


	// returns whether or not the mathform is constant
	public boolean analyzeStatic(float dur) {
		switch (type) {
		case 0:
			Fluent f = Problem.fluents.get(fluent);
			if (f.isConstant) {
				value = f.value;
				type = 1;
				return true;
			}
			return false;
		case 1:
			return true;
		case 2:
			return false;
		case 3:
			boolean res = true;
			res &= left.analyzeStatic(dur);
			res &= right.analyzeStatic(dur);
			if (!res)
				return false;
			type = 1;
			switch (op) {
			case '+':
				value = left.value + right.value;
				break;
			case '-':
				value = left.value - right.value;
				break;
			case '*':
				value = left.value * right.value;
				break;
			case '/':
				value = left.value / right.value;
				break;
			default:
				value = Float.NaN;
			}
			return true;
		case 5:
			if (dur == dur) {
				value = dur;
				type = 1;
				return true;
			}
			return false;
		case 4:
		case 6:
		default:
			return false;
		}
	}

	public boolean equals(Object obj) {
		MathForm o; 
		if (obj instanceof MathForm)
			o = (MathForm) obj;
		else
			return false;
		
		if (o.type != type)
			return false;
		switch(type) {
		case Constant:
			if (value != value)
				return false;
			break;
		case Fluent:
			if (fluent != fluent)
				return false;
			break;
		case Operator:
			if (op != o.op)
				return false;
			if (!left.equals(o.left) || !right.equals(o.right))
				return false;
			break;
		case Duration:
		case Start:
		case End:
		case DT:
			break;
		default:
			return false;		
		}
		
		return true;
	}

	/** Function to print this GMathForm. Mostly for debugging */
	public String toString() {
		if (type == MathForm.Fluent)
			return "(" + fluent + ')';

		if (type == MathForm.Constant)
			return Float.toString(value);

		if (type == MathForm.DT)
			return "#t";

		if (type == MathForm.Operator)
			return "(" + op + ' ' + left + ' ' + right + ')';

		if (type == MathForm.Start)
			return "start";

		if (type == MathForm.Duration)
			return "?duration";
		
		if (type == MathForm.End)
			return "end";

		return "NaN";
	}
}
